<template>
  <a-popover placement="bottomLeft" trigger="click">
    <template #content>
      <div style="width: 350px">
        <a-upload-dragger
            class="upload"
            v-model:fileList="fileList"
            :show-upload-list="false"
            name="file"
            :before-upload="beforeUpload"
            @change="handleUploadChange"
        >
          <img v-if="imageUrl" class="image" :src="imageUrl" alt="avatar"/>
          <div v-else class="upload-icon__wrapper">
            <div class="icon"><CloudUploadOutlined/></div>
            <div class="text">单击或拖动原型图到此区域进行上传</div>
          </div>
        </a-upload-dragger>

        <a-collapse
            class="collapse"
            style="margin-top: 10px"
            v-model:activeKey="collapseKeys"
        >
          <a-collapse-panel key="output" header="输出文本">
            <div v-if="content" class="output-wrapper">{{ content }}</div>
            <a-empty v-else :image="emptyIcon" />
          </a-collapse-panel>
        </a-collapse>
      </div>
    </template>

    <template #title>
      <div style="display: flex; justify-content: space-between; align-items: center">
        <div>
          <a-select
              ref="select"
              v-model:value="currentModel"
              style="width: 250px"
          >
            <a-select-option
                v-for="item in modelOptions"
                :key="item.value"
                :value="item.value"
            >{{ item.label }}
            </a-select-option>
          </a-select>
        </div>
        <div style="margin-right: 5px" v-if="loading">
          <LoadingOutlined/>
        </div>
      </div>
    </template>
    <a-button>
      <template #icon>
        <LoadingOutlined v-if="loading"/>
        <GlobalOutlined v-else/>
      </template>
      字段识别
    </a-button>
  </a-popover>
</template>

<script setup>

import {defineEmits, defineProps, ref} from 'vue'
import {message, Empty} from 'ant-design-vue'
import {CloudUploadOutlined, GlobalOutlined, LoadingOutlined} from '@ant-design/icons-vue'
import {fetchEventSource} from '@microsoft/fetch-event-source'

import { transformLabelToStr, guid } from '@/build/utils/index.js'

import Components from '@/build/config/component/index.js'
import Models from '@/build/model/index.js'

import {FORM_MESSAGE_CONTENT } from './content.js'

const QWEN_QVQ_72B_PREVIEW = 'Qwen/QVQ-72B-Preview'

// emit
const emits = defineEmits(['change'])
// props
const props = defineProps({
  tempType: {type: String, required: true, default: null}
})

const emptyIcon = Empty.PRESENTED_IMAGE_SIMPLE;
// 模型选择列表
const modelOptions = ref([
  { label: QWEN_QVQ_72B_PREVIEW, value: QWEN_QVQ_72B_PREVIEW }
])
// 当前模型
const currentModel = ref(QWEN_QVQ_72B_PREVIEW)
// 折叠面板Key
const collapseKeys = ref([])
// 输出文本
const content = ref('')

const fileList = ref([])

const imageUrl = ref('')

const loading = ref(false)

/**
 * @description 图片上传事件
 * @param {Object} info
 */
const handleUploadChange = async (info) => {
  imageUrl.value = await getBase64(info.file)

  try {
    loading.value = true

    if (currentModel.value === QWEN_QVQ_72B_PREVIEW) {
      switch (props.tempType) {
        // case 'form': {
        //   const res = {
        //     "type": "form",
        //     "dialog": false,
        //     "title": null,
        //     "grid": [
        //       [
        //         "参数编码",
        //         "参数标签",
        //         "参数名称",
        //         "参数类别"
        //       ],
        //       [
        //         "产品名称",
        //         "单位",
        //         "转换系数"
        //       ],
        //       [
        //         "异常值",
        //         "无效值",
        //         "精度"
        //       ],
        //       [
        //         "参数上区间",
        //         "参数下区间"
        //       ]
        //     ],
        //     "field": [
        //       {
        //         "label": "参数编码",
        //         "type": "Input",
        //         "required": false
        //       },
        //       {
        //         "label": "参数标签",
        //         "type": "Input",
        //         "required": false
        //       },
        //       {
        //         "label": "参数名称",
        //         "type": "Input",
        //         "required": false
        //       },
        //       {
        //         "label": "参数类别",
        //         "type": "Select",
        //         "required": false
        //       },
        //       {
        //         "label": "产品名称",
        //         "type": "Input",
        //         "required": false
        //       },
        //       {
        //         "label": "单位",
        //         "type": "Select",
        //         "required": false
        //       },
        //       {
        //         "label": "转换系数",
        //         "type": "Number",
        //         "required": false
        //       },
        //       {
        //         "label": "异常值",
        //         "type": "Input",
        //         "required": false
        //       },
        //       {
        //         "label": "无效值",
        //         "type": "Input",
        //         "required": false
        //       },
        //       {
        //         "label": "精度",
        //         "type": "Input",
        //         "required": false
        //       },
        //       {
        //         "label": "参数上区间",
        //         "type": "Range",
        //         "required": false
        //       },
        //       {
        //         "label": "参数下区间",
        //         "type": "Range",
        //         "required": false
        //       }
        //     ]
        //   }
        //
        //   const componentKeys = Object.entries(Components).map(([key, value]) => value.componentKey)
        //
        //   const fieldValue = []
        //
        //   const field = res.field.map(item => {
        //     const value = transformLabelToStr(item.label, fieldValue)
        //
        //     fieldValue.push(value)
        //
        //     return {
        //       uuid: crypto.randomUUID(),
        //       label: item.label,
        //       value: value,
        //       type: componentKeys.includes(item.type) ? item.type : Components.Input.componentKey,
        //       required: item.required
        //     }
        //   })
        //
        //   const fieldLabel = field.map(item => item.label)
        //
        //   const grid = res.grid.map((row, index) => {
        //     return {
        //       index,
        //       columns: row.filter(col => fieldLabel.includes(col)).map(col => {
        //         const fieldItem = field.find(item => item.label === col)
        //
        //         return {
        //           uuid: fieldItem.uuid,
        //           label: col,
        //           span: 6
        //         }
        //       })
        //     }
        //   })
        //
        //   emits('change', {
        //     fileConfig: {
        //       tempType: 'form'
        //     },
        //     modelConfig: {
        //       dialog: res.dialog,
        //       dialogTitle: res.title,
        //       field,
        //       grid,
        //     }
        //   })
        // } break;
        case 'form': await getChatCompletions({
          prompt: Models.form.prompt(imageUrl.value),
          onmessage: (ev) => {
            if (ev.data !== '[DONE]') {
              const data = JSON.parse(ev.data)
              const char = data?.choices?.[0]?.delta?.content ?? ''
              content.value = content.value + char
            }
          },
          onclose: (res) => {
            console.log('识别内容', res)

            const componentKeys = Object.entries(Components).map(([key, value]) => value.componentKey)

            const fieldValue = []

            const field = res.field.map(item => {
              const value = transformLabelToStr(item.label, fieldValue)

              fieldValue.push(value)

              return {
                uuid: guid(),
                label: item.label,
                value: value,
                type: componentKeys.includes(item.type) ? item.type : Components.Input.componentKey,
                required: item.required
              }
            })

            const fieldLabel = field.map(item => item.label)

            const grid = res.grid.map((row, index) => {

              const columnFilter = row.filter(col => fieldLabel.includes(col))
              const columns = columnFilter.map(col => {
                const fieldItem = field.find(item => item.label === col)

                return {
                  uuid: fieldItem.uuid,
                  label: col,
                  span: 24 / columnFilter.length
                }
              })

              return {index, columns}
            })

            emits('change', {
              fileConfig: {
                tempType: 'form'
              },
              modelConfig: {
                dialog: res.dialog ? 'yes' : 'no',
                dialogTitle: res.title ? res.title.replace(/新建|新增|编辑|修改|查看|详情/g, '') : null,
                dialogEdit: res.title ? new RegExp(/新建|新增|编辑|修改|查看|详情/).test(res.title) : false,
                field,
                grid,
              }
            })
          }
        }); break;
        case 'table': {

        }
      }
    }

    loading.value = false
  } catch (e) {
    console.log(e)
    loading.value = false
  }
}

const getChatCompletions = async ({ prompt, onmessage, onclose}) => {
  try {

    // console.log('extractJSON', extractJSON(`这个表单看起来是在一个网页上，没有明显的弹窗结构，所以dialog应该是false。 表单包含以下几个字段： 1. 编码：输入框，带红色星号，必填。 2. 名称：输入框，带红色星号，必填。 3. 产品类别：下拉选择框，带红色星号，必填，选项有“动力电池”。 4. 编码类型：下拉选择框，没有红色星号，非必填。 5. 开始位：输入框，没有红色星号，非必填。 6. 结束位：输入框，没有红色星号，非必填。 7. 备注：文本域，没有红色星号，非必填。 根据这些信息，可以得到以下JSON结构： { "dialog": false, "title": null, "grid": [ ["编码", "名称", "产品类别"], ["编码类型", "开始位", "结束位"], ["备注"] ], "field": [ { "label": "编码", "type": "Input", "required": true }, { "label": "名称", "type": "Input", "required": true }, { "label": "产品类别", "type": "Select", "required": true }, { "label": "编码类型", "type": "Select", "required": false }, { "label": "开始位", "type": "Input", "required": false }, { "label": "结束位", "type": "Input", "required": false }, { "label": "备注", "type": "Textarea", "required": false } ] } 我再检查一下，确保没有遗漏。 - dialog：不是弹窗，false正确。 - title：没有弹窗标题，设为null。 - grid：字段布局符合表格形式，第一行是“编码”、“名称”、“产品类别”，第二行是“编码类型”、“开始位”、“结束位”，第三行是“备注”。 - field：每个字段的label、type和required都正确对应。 看起来是正确的。 **最终答案** \\boxed{\\text{dialog: false, title: null, grid: \\{[编码, 名称, 产品类别], [编码类型, 开始位, 结束位], [备注]\\}, field: \\{ \\{label: 编码, type: Input, required: true\\}, \\{label: 名称, type: Input, required: true\\}, \\{label: 产品类别, type: Select, required: true\\}, \\{label: 编码类型, type: Select, required: false\\}, \\{label: 开始位, type: Input, required: false\\}, \\{label: 结束位, type: Input, required: false\\}, \\{label: 备注, type: Textarea, required: false\\} \\}}}`))
    // return
    content.value = ''

    await fetchEventSource('https://api.siliconflow.cn/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
        Authorization: 'Bearer sk-frbhguggwrzqtcxqpvxahbldwdmxytfcwrwxhwobogqjvrsp'
      },
      body: JSON.stringify({
        'model': QWEN_QVQ_72B_PREVIEW,
        'frequency_penalty': 0,
        'max_tokens': 8192,
        'stream': true,
        'temperature': 0.7,
        'top_k': 50,
        'top_p': 0.7,
        'messages': prompt
      }),
      onmessage: (ev) => {
        onmessage(ev)
      },
      onclose: () => {
        const ctx = extractJSON(content.value)
        console.log('onclose', ctx)
        if (ctx.error) {
          message.error('识别失败')
          loading.value = false
        } else {
          onclose(ctx)
        }
      },
      onerror: (err) => {
        throw err
      }
    })
  } catch (e) {
    console.log(e)
  }
}

const extractJSON = (str) => {
  const candidates = [];
  const regex = /({|\[)(?:\\.|[^\\{}]|{(?:\\.|[^\\{}])*})*?}(?=\s*$|[\s.,;]|\\boxed)/g;

  let match;

  while ((match = regex.exec(str)) !== null) {
    try {
      const obj = JSON.parse(match[0]);
      candidates.push(obj);
    } catch {
      candidates.push(match[0]);
      console.log('candidates catch', match[0])
    }
  }

  console.log('candidates', candidates)

  // 如果有符合规则的对象直接返回
  for (let i = candidates.length - 1; i >= 0; i--) {
    const candidate = candidates[i];
    if (typeof candidate === 'object' && 'dialog' in candidate) {
      return candidate;
    }
  }
  // 处理非常规格式
  // for (let i = candidates.length - 1; i >= 0; i--) {
  //   const candidate = candidates[i];
  //
  //   if (typeof candidate === 'string') {
  //     const repaired = repairCandidate(candidate);
  //     console.log('深度修复', repaired)
  //
  //     if (repaired && 'dialog' in repaired) {
  //       return repaired
  //     }
  //   }
  // }

  console.log('阶段2', candidates)

  // 阶段4：最终保底方案
  return { error: "No valid JSON found", raw: str };

  // 修复工具函数
  // function repairCandidate(raw) {
  //   try {
  //     // 清理转义字符并标准化字符串结构
  //     let formattedStr = raw
  //         // 阶段1: 清理外层结构
  //         .replace(/^[^{]*{\\?text{?/g, '{')  // 移除{\text前缀
  //         .replace(/}\s*}$/g, '}')          // 修复多余闭合符号
  //         .replace(/\\/g, '') // 移除所有反斜杠
  //         .replace(/([{,]\s*)(\w+)(\s*:)/g, '$1"$2"$3') // 为键添加双引号
  //         .replace(/"grid":\s*{/g, '"grid": [') // 转换grid外层括号
  //         .replace(/}\s*,\s*"field"/g, '], "field"') // 闭合grid数组
  //         .replace(/"field":\s*{/g, '"field": [') // 转换field外层括号
  //         .replace(/}\s*}\s*$/g, '}]}'); // 闭合field数组和整个对象
  //
  //     // 处理数组中的字符串元素
  //     formattedStr = formattedStr.replace(/($)([^$]+?)(\])/g, (_, open, content, close) => {
  //           const items = content.split(',').map(item => {
  //             const val = item.trim().replace(/^"+|"+$/g, '');
  //             return val.match(/^(null|true|false|\d+)$/) ? val : `"${val}"`;
  //           });
  //           return `${open}${items.join(', ')}${close}`;
  //         }
  //     );
  //
  //     // 处理对象属性值
  //     formattedStr = formattedStr.replace(/({|,)\s*"(\w+)":\s*([^,}]+)(?=\s*[,}])/g, (match, prefix, key, value) => {
  //           const trimmed = value.trim();
  //           if (['true', 'false', 'null'].includes(trimmed)) {
  //             return `${prefix}"${key}": ${trimmed}`;
  //           }
  //           return `${prefix}"${key}": "${trimmed}"`;
  //         }
  //     );
  //
  //     console.log('repairCandidate', formattedStr)
  //
  //     const balanced = balanceBraces(formattedStr);
  //
  //     console.log('balanced', balanced)
  //     return JSON.parse(balanced);
  //   } catch (e) {
  //     return null;
  //   }
  // }

  // 括号平衡函数
  function balanceBraces(str) {
    let stack = 0;
    const chars = str.split('');

    chars.forEach((c, i) => {
      if (c === '{') stack++;
      if (c === '}') stack--;
      if (stack < 0) {
        chars[i] = '';
        stack = 0;
      }
    });

    return chars.join('') + '}'.repeat(stack);
  }
}


const getBase64 = (file) => {
  console.log('getBase64', file)
  return new Promise((resolve, reject) => {
    // 创建一个新的 FileReader 对象
    const reader = new FileReader();
    // 读取 File 对象
    reader.readAsDataURL(file);
    // 加载完成后
    reader.onload = function () {
      // 将读取的数据转换为 base64 编码的字符串
      // const base64String = reader.result.split(",")[1];
      // 解析为 Promise 对象，并返回 base64 编码的字符串
      resolve(reader.result);
    };

    // 加载失败时
    reader.onerror = function () {
      reject(new Error("Failed to load file"));
    };
  });

}

const beforeUpload = file => {
  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png'

  if (!isJpgOrPng) {
    message.error('只能上传jpg/jpeg/png类型的图片!');
  }

  const isLt2M = file.size / 1024 / 1024 < 2;

  if (!isLt2M) {
    message.error('图片必须小于2MB!');
  }

  if (isJpgOrPng && isLt2M) {
    fileList.value = [...(fileList.value || []), file]
  }

  return false
};


</script>

<style lang="scss">
.ant-upload.ant-upload-btn {
  padding: 0 !important;

  .image {
    width: 348px;
    border-radius: 8px;
    overflow: hidden;
  }

  .upload-icon__wrapper {
    padding: 16px 0;

    .icon {
      font-size: 30px;
      color: #1677ff;
    }

    .text {
      font-size: 12px;
      color: #131414;
    }
  }
}

.collapse {
  .ant-collapse-content-box {
    padding: 8px 0 8px 8px !important;
    min-height: 200px;
    max-height: 300px;
    overflow-y: auto;

    &::-webkit-scrollbar {
      width: 5px;
      background-color: #f5f5f5;
    }
    &::-webkit-scrollbar-thumb {
      background-color: rgba(0, 0, 0, 0.5);
      border-radius: 99px;
    }
    &::-webkit-scrollbar-thumb:hover {
      background-color: rgba(0, 0, 0, 0.5);
    }
  }

  .output-wrapper {
    width: 100%;
    word-break: break-all;
    padding-right: 8px;
  }
}

</style>
